import json
import os
import re
from collections import Counter
from typing import Union, Dict, Optional

import fitz

from app.utils.pymupdf_tools import pdf_get_content_region, deduplicate_spans_in_blocks_with_stats, extract_json, \
    analyze_text_heights, analyze_spans_keys, analyze_spans_value_distribution
from tests.base_test import base_test_case

logger = base_test_case.get_logger(__name__)
TEST_DATA_DIR = base_test_case.test_data_dir
OUTPUT_DATA_DIR = base_test_case.output_data_dir

""""分析PDF文档结构测试 """
def extract_detailed_text_features(pdf_path, page_number=0):
    """
    提取PDF页面的详细文本特征用于分析

    Args:
        pdf_path: PDF文件路径
        page_number: 页面编号

    Returns:
        dict: 包含详细文本特征的数据
    """
    doc = fitz.open(pdf_path)
    page = doc[page_number]

    # 获取页面详细文本信息
    text_dict = page.get_text('dict', sort=True)
    page_width = text_dict['width']
    page_height = text_dict['height']

    # 收集所有文本特征
    features = {
        'page_size': (page_width, page_height),
        'spans': [],
        'lines': [],
        'blocks': []
    }

    # 提取所有span级别的特征
    for block_idx, block in enumerate(text_dict.get('blocks', [])):
        if block.get('type') != 0:  # 只处理文本块
            continue

        block_info = {
            'block_id': block_idx,
            'bbox': block.get('bbox', [0, 0, 0, 0]),
            'line_count': len(block.get('lines', [])),
            'text': extract_text_from_block(block)
        }

        for line_idx, line in enumerate(block.get('lines', [])):
            line_info = {
                'block_id': block_idx,
                'line_id': line_idx,
                'bbox': line.get('bbox', [0, 0, 0, 0]),
                'span_count': len(line.get('spans', []))
            }

            for span_idx, span in enumerate(line.get('spans', [])):
                span_info = {
                    'block_id': block_idx,
                    'line_id': line_idx,
                    'span_id': span_idx,
                    'text': span.get('text', '').strip(),
                    'font': span.get('font', ''),
                    'size': span.get('size', 0),
                    'color': span.get('color', 0),
                    'ascender': span.get('ascender', 0),
                    'descender': span.get('descender', 0),
                    'bbox': span.get('bbox', [0, 0, 0, 0]),
                    'flags': span.get('flags', 0),
                    'origin': span.get('origin', [0, 0])  # 文字起始位置
                }

                # 计算一些衍生特征
                bbox = span_info['bbox']
                if bbox[2] > bbox[0] and bbox[3] > bbox[1]:
                    span_info['width'] = bbox[2] - bbox[0]
                    span_info['height'] = bbox[3] - bbox[1]
                else:
                    span_info['width'] = 0
                    span_info['height'] = 0

                # 计算位置特征
                span_info['x_position'] = bbox[0]  # 距离页面左侧距离
                span_info['y_position'] = bbox[1]  # 距离页面顶部距离
                span_info['x_ratio'] = bbox[0] / page_width if page_width > 0 else 0  # 相对横向位置
                span_info['y_ratio'] = bbox[1] / page_height if page_height > 0 else 0  # 相对纵向位置

                # 文本特征
                span_info['char_count'] = len(span_info['text'])
                span_info['is_bold'] = bool(span_info['flags'] & 2 ** 4)  # 检查粗体标志
                span_info['is_italic'] = bool(span_info['flags'] & 2 ** 1)  # 检查斜体标志

                features['spans'].append(span_info)

            features['lines'].append(line_info)

        features['blocks'].append(block_info)

    doc.close()
    return features


def analyze_text_patterns(features):
    """
    分析文本模式以识别不同类型的元素

    Args:
        features: 从extract_detailed_text_features获取的特征数据

    Returns:
        dict: 分析结果
    """
    # 统计各种特征
    font_stats = {}
    size_stats = {}
    color_stats = {}

    for span in features['spans']:
        font = span['font']
        size = span['size']
        color = span['color']

        font_stats[font] = font_stats.get(font, 0) + 1
        size_stats[size] = size_stats.get(size, 0) + 1
        color_stats[color] = color_stats.get(color, 0) + 1

    # 找出最常见的字体、大小和颜色
    common_font = max(font_stats, key=font_stats.get) if font_stats else ''
    common_size = max(size_stats, key=size_stats.get) if size_stats else 0
    common_color = max(color_stats, key=color_stats.get) if color_stats else 0

    # 计算大小分布
    sizes = list(size_stats.keys())
    avg_size = sum(s * size_stats[s] for s in sizes) / sum(size_stats.values()) if size_stats else 0
    max_size = max(sizes) if sizes else 0
    min_size = min(sizes) if sizes else 0

    return {
        'font_stats': font_stats,
        'size_stats': size_stats,
        'color_stats': color_stats,
        'common_font': common_font,
        'common_size': common_size,
        'common_color': common_color,
        'avg_size': avg_size,
        'max_size': max_size,
        'min_size': min_size
    }


def classify_text_elements(features, patterns):
    """
    基于特征和模式分类文本元素

    Args:
        features: 文本特征
        patterns: 分析出的模式

    Returns:
        dict: 分类结果
    """
    page_width, page_height = features['page_size']

    # 初始化分类结果
    headers = []
    titles = []
    paragraphs = []
    question_numbers = []
    answers = []
    footers = []

    # 定义分类阈值
    header_top_threshold = page_height * 0.15  # 顶部15%
    footer_bottom_threshold = page_height * 0.85  # 底部15%

    # 大字体阈值（比平均值大20%）
    large_font_threshold = patterns['avg_size'] * 1.2
    # 小字体阈值（比平均值小20%）
    small_font_threshold = patterns['avg_size'] * 0.8

    # 按块处理
    for block in features['blocks']:
        block_spans = [span for span in features['spans'] if span['block_id'] == block['block_id']]
        if not block_spans:
            continue

        # 获取块的边界框
        block_bbox = block['bbox']
        x0, y0, x1, y1 = block_bbox

        # 根据位置分类
        if y0 < header_top_threshold:
            headers.append(block)
        elif y1 > footer_bottom_threshold:
            footers.append(block)
        else:
            # 根据内容和格式特征分类
            block_classification = classify_block(block_spans, patterns, large_font_threshold, small_font_threshold)

            if block_classification == 'title':
                titles.append(block)
            elif block_classification == 'question_number':
                question_numbers.append(block)
            elif block_classification == 'answer':
                answers.append(block)
            else:
                paragraphs.append(block)

    return {
        'headers': headers,
        'footers': footers,
        'titles': titles,
        'paragraphs': paragraphs,
        'question_numbers': question_numbers,
        'answers': answers
    }


def classify_block(spans, patterns, large_font_threshold, small_font_threshold):
    """
    分类单个文本块

    Args:
        spans: 块中的所有span
        patterns: 文本模式
        large_font_threshold: 大字体阈值
        small_font_threshold: 小字体阈值

    Returns:
        str: 块的分类
    """
    if not spans:
        return 'paragraph'

    # 合并所有文本
    full_text = ''.join(span['text'] for span in spans).strip()

    # 特征统计
    avg_size = sum(span['size'] for span in spans) / len(spans)
    max_size = max(span['size'] for span in spans)
    min_size = min(span['size'] for span in spans)

    # 检查是否有粗体
    has_bold = any(span['is_bold'] for span in spans)

    # 检查位置特征
    first_span = spans[0]
    x_position = first_span['x_ratio']  # 相对横向位置
    char_count = sum(span['char_count'] for span in spans)

    # 判断是否为标题
    # 1. 字体较大且字符数适中
    if avg_size >= large_font_threshold and 2 <= char_count <= 200:
        return 'title'

    # 2. 特别大的字体
    if max_size >= large_font_threshold * 1.3 and char_count <= 100:
        return 'title'

    # 判断是否为题号
    # 1. 字符数少且以数字或特定模式开头
    import re
    question_patterns = [
        r'^(\d+\.|\d+、|\(\d+\)|\[\d+\]|第\d题)',
        r'^(\d+\.\d+)',
        r'^([一二三四五六七八九十]+[\.、]?)',
        r'^(\d+\))',
        r'^【\d+】',
        r'^\d+\s*分\)?$',
        r'^【案例分析题】',
        r'^(一|二|三|四|五|六|七|八|九|十)[\.、]',
        r'^(学习指南|考情分析|本章重点|知识要点)'
    ]

    for pattern in question_patterns:
        if re.match(pattern, full_text):
            # 题号通常较短且可能有特殊格式
            if char_count <= 30:
                return 'question_number'
            else:
                return 'title'  # 较长的匹配可能是标题

    # 判断是否为答案
    # 1. 特殊的缩进
    if x_position > 0.1:  # 缩进超过页面宽度的10%
        return 'answer'

    # 2. 使用粗体且不是标题大小
    if has_bold and avg_size < large_font_threshold:
        return 'answer'

    return 'paragraph'


def print_detailed_analysis(features, patterns, classification):
    """
    打印详细分析结果

    Args:
        features: 文本特征
        patterns: 模式分析结果
        classification: 分类结果
    """
    print("=" * 60)
    print("PDF页面详细结构分析结果")
    print("=" * 60)

    page_width, page_height = features['page_size']
    print(f"页面尺寸: {page_width:.2f} x {page_height:.2f}")

    print(f"\n字体统计 (共{len(patterns['font_stats'])}种字体):")
    sorted_fonts = sorted(patterns['font_stats'].items(), key=lambda x: x[1], reverse=True)
    for font, count in sorted_fonts[:5]:  # 显示前5种常用字体
        print(f"  - {font}: {count} 次")

    print(f"\n字体大小分布:")
    print(f"  - 平均大小: {patterns['avg_size']:.2f}")
    print(f"  - 最大大小: {patterns['max_size']:.2f}")
    print(f"  - 最小大小: {patterns['min_size']:.2f}")
    print(f"  - 大字体阈值: {patterns['avg_size'] * 1.2:.2f}")

    print(f"\n颜色统计 (共{len(patterns['color_stats'])}种颜色):")
    sorted_colors = sorted(patterns['color_stats'].items(), key=lambda x: x[1], reverse=True)
    for color, count in sorted_colors[:3]:  # 显示前3种常用颜色
        print(f"  - {color}: {count} 次")

    print(f"\n页眉数量: {len(classification['headers'])}")
    for header in classification['headers']:
        print(f"  - {header['text'][:50]}...")

    print(f"\n页脚数量: {len(classification['footers'])}")
    for footer in classification['footers']:
        print(f"  - {footer['text'][:50]}...")

    print(f"\n标题数量: {len(classification['titles'])}")
    for title in classification['titles']:
        print(f"  - {title['text']}")

    print(f"\n题号数量: {len(classification['question_numbers'])}")
    for qnum in classification['question_numbers']:
        print(f"  - {qnum['text']}")

    print(f"\n答案数量: {len(classification['answers'])}")
    for answer in classification['answers']:
        print(f"  - {answer['text'][:50]}...")

    print(f"\n段落数量: {len(classification['paragraphs'])}")
    for i, para in enumerate(classification['paragraphs'][:3]):  # 只显示前3个
        print(f"  - {para['text'][:100]}...")
    if len(classification['paragraphs']) > 3:
        print(f"  ... 还有 {len(classification['paragraphs']) - 3} 个段落")


def analyze_pdf_structure(pdf_path, page_number=0):
    """
    分析PDF页面结构，识别页眉、标题、段落、题号等内容

    Args:
        pdf_path: PDF文件路径
        page_number: 页面编号

    Returns:
        dict: 包含各类元素的分析结果
    """
    doc = fitz.open(pdf_path)
    page = doc[page_number]

    # 获取页面详细文本信息
    text_dict = page.get_text('dict', sort=True)
    page_width = text_dict['width']
    page_height = text_dict['height']

    # 初始化各类元素容器
    headers = []  # 页眉
    titles = []  # 标题
    paragraphs = []  # 段落
    question_numbers = []  # 题号
    question_contents = []  # 问题内容
    answers = []  # 答案
    footers = []  # 页脚

    # 收集所有块的字体大小信息，用于统计分析
    all_font_sizes = []
    for block in text_dict.get('blocks', []):
        if block.get('type') != 0:  # 只处理文本块
            continue
        for line in block.get('lines', []):
            for span in line.get('spans', []):
                size = span.get('size', 0)
                if size > 0:
                    all_font_sizes.append(size)

    # 计算字体大小的统计信息
    if all_font_sizes:
        avg_font_size = sum(all_font_sizes) / len(all_font_sizes)
        max_font_size = max(all_font_sizes)
        min_font_size = min(all_font_sizes)
    else:
        avg_font_size = 12
        max_font_size = 16
        min_font_size = 10

    # 提取详细特征
    features = extract_detailed_text_features(pdf_path, page_number)

    # 分析模式
    patterns = analyze_text_patterns(features)

    # 分类文本元素
    classification = classify_text_elements(features, patterns)

    # 转换分类结果为旧格式
    for header_block in classification['headers']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == header_block['bbox']), None)
        if block_data:
            headers.append({
                'block_id': header_block['block_id'],
                'bbox': header_block['bbox'],
                'content': header_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {}
            })

    for footer_block in classification['footers']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == footer_block['bbox']), None)
        if block_data:
            footers.append({
                'block_id': footer_block['block_id'],
                'bbox': footer_block['bbox'],
                'content': footer_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {}
            })

    for title_block in classification['titles']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == title_block['bbox']), None)
        if block_data:
            titles.append({
                'block_id': title_block['block_id'],
                'bbox': title_block['bbox'],
                'content': title_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {},
                'analysis': analyze_text_block(block_data, title_block['block_id']) if block_data else {}
            })

    for qnum_block in classification['question_numbers']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == qnum_block['bbox']), None)
        if block_data:
            question_numbers.append({
                'block_id': qnum_block['block_id'],
                'bbox': qnum_block['bbox'],
                'content': qnum_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {},
                'analysis': analyze_text_block(block_data, qnum_block['block_id']) if block_data else {}
            })

    for answer_block in classification['answers']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == answer_block['bbox']), None)
        if block_data:
            answers.append({
                'block_id': answer_block['block_id'],
                'bbox': answer_block['bbox'],
                'content': answer_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {},
                'analysis': analyze_text_block(block_data, answer_block['block_id']) if block_data else {}
            })

    for para_block in classification['paragraphs']:
        block_data = next((b for b in text_dict['blocks'] if
                           b.get('bbox') == para_block['bbox']), None)
        if block_data:
            paragraphs.append({
                'block_id': para_block['block_id'],
                'bbox': para_block['bbox'],
                'content': para_block['text'],
                'properties': analyze_block_properties(block_data) if block_data else {},
                'analysis': analyze_text_block(block_data, para_block['block_id']) if block_data else {}
            })

    doc.close()

    result = {
        'headers': headers,
        'footers': footers,
        'titles': titles,
        'paragraphs': paragraphs,
        'question_numbers': question_numbers,
        'question_contents': question_contents,
        'answers': answers,
        'page_size': (page_width, page_height),
        'font_stats': {
            'avg_size': avg_font_size,
            'max_size': max_font_size,
            'min_size': min_font_size
        }
    }

    # 打印详细分析结果
    print_detailed_analysis(features, patterns, classification)

    return result


def extract_text_from_block(block):
    """
    从文本块中提取完整文本内容

    Args:
        block: 文本块字典

    Returns:
        str: 提取的文本内容
    """
    text = ""
    for line in block.get('lines', []):
        for span in line.get('spans', []):
            text += span.get('text', '')
        text += '\n'  # 每行结束后添加换行符
    return text.strip()


def analyze_block_properties(block):
    """
    分析文本块的属性特征

    Args:
        block: 文本块字典

    Returns:
        dict: 属性分析结果
    """
    properties = {
        'fonts': set(),
        'sizes': set(),
        'colors': set(),
        'total_chars': 0
    }

    for line in block.get('lines', []):
        for span in line.get('spans', []):
            # 收集字体信息
            font = span.get('font', '')
            size = span.get('size', 0)
            color = span.get('color', 0)

            if font:
                properties['fonts'].add(font)
            if size:
                properties['sizes'].add(round(size, 1))
            if color:
                properties['colors'].add(color)

            # 统计字符数量
            properties['total_chars'] += len(span.get('text', ''))

    # 转换集合为列表以便JSON序列化
    properties['fonts'] = list(properties['fonts'])
    properties['sizes'] = list(properties['sizes'])
    properties['colors'] = list(properties['colors'])

    return properties


def analyze_text_block(block, block_id):
    """
    详细分析文本块的结构特征

    Args:
        block: 文本块字典
        block_id: 块ID

    Returns:
        dict: 详细的分析结果
    """
    analysis = {
        'block_id': block_id,
        'line_count': len(block.get('lines', [])),
        'lines': [],
        'bbox': block.get('bbox', [0, 0, 0, 0])
    }

    total_chars = 0
    font_sizes = []
    fonts = []
    colors = []
    line_heights = []
    line_spacings = []

    prev_line_bottom = None
    for line_idx, line in enumerate(block.get('lines', [])):
        line_info = {
            'line_id': line_idx,
            'spans': [],
            'char_count': 0,
            'bbox': line.get('bbox', [0, 0, 0, 0])
        }

        # 计算行高
        line_bbox = line.get('bbox', [0, 0, 0, 0])
        if line_bbox[3] > line_bbox[1]:  # 确保高度有效
            line_height = line_bbox[3] - line_bbox[1]
            line_heights.append(line_height)

        # 计算行间距
        if prev_line_bottom is not None:
            line_spacing = line_bbox[1] - prev_line_bottom
            line_spacings.append(line_spacing)
        prev_line_bottom = line_bbox[3]

        for span_idx, span in enumerate(line.get('spans', [])):
            span_info = {
                'span_id': span_idx,
                'text': span.get('text', ''),
                'font': span.get('font', ''),
                'size': span.get('size', 0),
                'color': span.get('color', 0),
                'flags': span.get('flags', 0),
                'char_count': len(span.get('text', ''))
            }

            line_info['spans'].append(span_info)
            line_info['char_count'] += span_info['char_count']

            # 收集整体统计信息
            total_chars += span_info['char_count']
            font_sizes.append(span_info['size'])
            fonts.append(span_info['font'])
            colors.append(span_info['color'])

        analysis['lines'].append(line_info)

    analysis['total_chars'] = total_chars
    analysis['avg_font_size'] = sum(font_sizes) / len(font_sizes) if font_sizes else 0
    analysis['max_font_size'] = max(font_sizes) if font_sizes else 0
    analysis['min_font_size'] = min(font_sizes) if font_sizes else 0
    analysis['avg_line_height'] = sum(line_heights) / len(line_heights) if line_heights else 0
    analysis['avg_line_spacing'] = sum(line_spacings) / len(line_spacings) if line_spacings else 0
    analysis['unique_fonts'] = list(set(fonts))
    analysis['unique_colors'] = list(set(colors))

    # 计算字体大小变化
    if len(set(font_sizes)) > 1:
        analysis['font_size_variation'] = max(font_sizes) - min(font_sizes)
    else:
        analysis['font_size_variation'] = 0

    return analysis


def is_title(block_analysis):
    """
    判断是否为标题

    Args:
        block_analysis: 块分析结果

    Returns:
        bool: 是否为标题
    """
    # 标题特征：
    # 1. 字体较大
    # 2. 行数较少
    # 3. 字符总数适中
    # 4. 通常在页面上部
    # 5. 可能有较大的字体变化

    avg_size = block_analysis.get('avg_font_size', 0)
    line_count = block_analysis.get('line_count', 0)
    total_chars = block_analysis.get('total_chars', 0)
    max_size = block_analysis.get('max_font_size', 0)
    font_variation = block_analysis.get('font_size_variation', 0)

    # 收集所有文本内容进行进一步分析
    content = ""
    for line in block_analysis.get('lines', []):
        for span in line.get('spans', []):
            content += span.get('text', '')

    content = content.strip()

    # 字体较大且行数少，可能是标题
    # 降低阈值，适应更多情况
    if avg_size >= 12 and line_count <= 5 and 1 <= total_chars <= 300:
        return True

    # 特别大的字体，即使字符数较多也可能是标题
    if max_size >= 16 and total_chars <= 100:
        return True

    # 字体变化较大，可能是标题
    if font_variation >= 4 and line_count <= 3:
        return True

    # 检查是否以特定模式开头（如章节标题）
    import re
    chapter_patterns = [
        r'^(第[一二三四五六七八九十\d]+章|第[一二三四五六七八九十\d]+节)',
        r'^(\d+\.\d*|\d+、)',
        r'^(学习指南|本章重点|知识要点|考情分析)'
    ]

    for pattern in chapter_patterns:
        if re.match(pattern, content):
            return True

    return False


def is_question_number(block_analysis):
    """
    判断是否为题号

    Args:
        block_analysis: 块分析结果

    Returns:
        bool: 是否为题号
    """
    # 题号特征：
    # 1. 通常以数字开头，后面可能跟点或括号
    # 2. 字符数较少
    # 3. 可能有特殊的格式
    # 4. 通常在段落开始位置，可能有特殊格式

    if block_analysis.get('line_count', 0) != 1:
        return False

    if block_analysis.get('total_chars', 0) > 30:  # 增加字符数限制
        return False

    # 检查第一行第一个跨度
    if block_analysis.get('lines', []):
        first_line = block_analysis['lines'][0]
        if first_line.get('spans', []):
            first_span = first_line['spans'][0]
            text = first_span.get('text', '').strip()

            # 匹配题号格式，如："1." "1、" "(1)" "[1]"等
            import re
            patterns = [
                r'^(\d+\.|\d+、|\(\d+\)|\[\d+\]|第\d题)',  # 原有模式
                r'^(\d+\.\d+)',  # 子题号如 1.1
                r'^([一二三四五六七八九十]+[\.、]?)',  # 中文数字题号
                r'^(\d+\))',  # 数字加右括号
                r'^【\d+】',  # 方括号数字
                r'^\d+\s*分\)?$',  # 分数标记
                r'^【案例分析题】',  # 特殊题型标记
                r'^(一|二|三|四|五|六|七|八|九|十)[\.、]'  # 中文序号
            ]

            for pattern in patterns:
                if re.match(pattern, text):
                    return True

    return False


def is_answer(block_analysis):
    """
    判断是否为答案，基于格式特征而非固定文字

    Args:
        block_analysis: 块分析结果

    Returns:
        bool: 是否为答案
    """
    # 答案特征：
    # 1. 可能有特殊的缩进
    # 2. 可能有特殊的字体或颜色
    # 3. 可能有特殊的行间距
    # 4. 通常在问题之后

    # 检查是否有特殊的缩进（第一行相对于其他行有较大缩进）
    lines = block_analysis.get('lines', [])
    if len(lines) < 2:
        return False

    # 获取每行的左边界
    left_positions = []
    for line in lines:
        bbox = line.get('bbox', [0, 0, 0, 0])
        left_positions.append(bbox[0])

    # 如果第一行明显比其他行靠右，可能是答案的标识性缩进
    if len(left_positions) > 1:
        first_left = left_positions[0]
        avg_other_left = sum(left_positions[1:]) / (len(left_positions) - 1)
        if first_left > avg_other_left + 10:  # 10点的缩进差异
            return True

    # 检查是否有特殊的字体变化（如答案关键词使用了不同字体）
    unique_fonts = block_analysis.get('unique_fonts', [])
    if len(unique_fonts) > 2:  # 多种字体混合使用
        # 检查是否某一行使用了特别的字体
        first_line_fonts = set()
        if lines:
            for span in lines[0].get('spans', []):
                if span.get('font'):
                    first_line_fonts.add(span['font'])

        # 如果第一行使用了与其他行不同的字体，可能是答案标识
        if len(first_line_fonts) == 1 and not first_line_fonts.issubset(set(unique_fonts[1:])):
            return True

    return False


def save_page_json(pdf_path, page_number, output_path):
    """
    将指定页面的dict数据保存为JSON文件

    Args:
        pdf_path: PDF文件路径
        page_number: 页面编号
        output_path: 输出JSON文件路径
    """

    doc = fitz.open(pdf_path)
    page = doc[page_number]
    # 正文区域
    roi = pdf_get_content_region(page, zoom_factor=1.0)
    text_dict = page.get_text('dict', clip=roi, sort=True)

    # 去除重复的span
    deduplicated_dict = deduplicate_spans_in_blocks_with_stats(text_dict)
    analyze_text_heights(deduplicated_dict, verbose=True)
    analyze_spans_keys(deduplicated_dict, verbose=True)
    analyze_spans_value_distribution(deduplicated_dict, verbose=True)

    # 分析结构
    structure = classify_text_structure(
        text_dict,
        page_width=page.rect.width,  # 更准确的页面宽度
        verbose=True
    )

    # 获取所有题干
    for q in structure["questions"]:
        print("题干:", q["full_question"])

    json = extract_json(deduplicated_dict)
    doc.close()
    with open(output_path, 'w') as file:
        file.write(json)
    # with open(output_path, 'w', encoding='utf-8') as f:
    #     json.dump(json, f, ensure_ascii=False, indent=2)


def print_analysis_summary(analysis_result):
    """
    打印分析结果摘要

    Args:
        analysis_result: analyze_pdf_structure函数的返回结果
    """
    print("=" * 50)
    print("PDF页面结构分析结果")
    print("=" * 50)

    page_width, page_height = analysis_result['page_size']
    print(f"页面尺寸: {page_width:.2f} x {page_height:.2f}")

    print(f"\n页眉数量: {len(analysis_result['headers'])}")
    for header in analysis_result['headers']:
        print(f"  - {header['content'][:50]}...")

    print(f"\n页脚数量: {len(analysis_result['footers'])}")
    for footer in analysis_result['footers']:
        print(f"  - {footer['content'][:50]}...")

    print(f"\n标题数量: {len(analysis_result['titles'])}")
    for title in analysis_result['titles']:
        print(f"  - {title['content']}")
        props = title['properties']
        print(f"    字体: {props['fonts']}, 大小: {props['sizes']}")

    print(f"\n题号数量: {len(analysis_result['question_numbers'])}")
    for qnum in analysis_result['question_numbers']:
        print(f"  - {qnum['content']}")

    print(f"\n答案数量: {len(analysis_result['answers'])}")
    for answer in analysis_result['answers']:
        print(f"  - {answer['content'][:50]}...")

    print(f"\n段落数量: {len(analysis_result['paragraphs'])}")
    for i, para in enumerate(analysis_result['paragraphs'][:3]):  # 只显示前3个
        print(f"  - {para['content'][:100]}...")
    if len(analysis_result['paragraphs']) > 3:
        print(f"  ... 还有 {len(analysis_result['paragraphs']) - 3} 个段落")


def classify_text_structure(
        input_data: Union[str, Dict],
        page_width: float = 595.0,  # A4 宽度默认值（可从 page.rect.width 获取）
        verbose: bool = True
) -> Optional[Dict]:
    """
    基于统计特征识别标题、章节、试题结构。

    Args:
        input_data: JSON 路径 或 text_dict
        page_width: 页面宽度（用于判断居中），单位 pt
        verbose: 是否打印结果

    Returns:
        dict 包含分类结果
    """
    # === 1. 加载数据 ===
    if isinstance(input_data, str):
        if not os.path.isfile(input_data):
            raise FileNotFoundError(f"JSON 文件不存在: {input_data}")
        with open(input_data, 'r', encoding='utf-8') as f:
            data = json.load(f)
    elif isinstance(input_data, dict):
        data = input_data
    else:
        raise TypeError("input_data 必须是 str 或 dict")

    lines_features = []

    for block in data.get("blocks", []):
        if block.get("type") != 0:
            continue
        for line in block.get("lines", []):
            bbox = line.get("bbox")
            if not bbox or len(bbox) != 4:
                continue
            height = round(bbox[3] - bbox[1], 2)
            x0, y0, x1, y1 = bbox
            spans = line.get("spans", [])
            if not spans:
                continue

            # 聚合 span 特征
            texts = [s.get("text", "") for s in spans]
            full_text = "".join(texts).strip()
            if not full_text:
                continue

            sizes = [s.get("size", 0) for s in spans]
            fonts = [s.get("font", "") for s in spans]
            colors = [s.get("color", 0) for s in spans]
            flags_list = [s.get("flags", 0) for s in spans]

            avg_size = round(sum(sizes) / len(sizes), 2)
            main_font = Counter(fonts).most_common(1)[0][0]
            main_color = Counter(colors).most_common(1)[0][0]

            # 判断是否加粗：flags & 2^1 (bit 1) 或字体名含 Bold
            is_bold = any(
                (flag & 2) or "Bold" in font or "Black" in font
                for flag, font in zip(flags_list, fonts)
            )

            # 判断是否居中（宽松）
            line_center = (x0 + x1) / 2
            is_centered = abs(line_center - page_width / 2) < 30  # 容差 30pt

            # 判断是否为编号开头（题号/章节号）
            starts_with_number = bool(re.match(r'^\s*[\d\.\(\)【】\[\]ⅠⅡⅢIVXLCDM]+', full_text))
            starts_with_letter_option = bool(re.match(r'^\s*[A-Ea-e][\.\)]', full_text))
            starts_with_chinese_bracket = bool(re.match(r'^\s*【[^】]*】', full_text))

            lines_features.append({
                "text": full_text,
                "bbox": [round(x, 2) for x in bbox],
                "height": height,
                "avg_size": avg_size,
                "main_font": main_font,
                "main_color": main_color,
                "is_bold": is_bold,
                "is_centered": is_centered,
                "starts_with_number": starts_with_number,
                "starts_with_letter_option": starts_with_letter_option,
                "starts_with_chinese_bracket": starts_with_chinese_bracket,
                "y_top": y0  # 用于排序
            })

    if not lines_features:
        if verbose:
            print("⚠️ 未提取到有效文本行")
        return None

    # 按 y_top 排序（从上到下）
    lines_features.sort(key=lambda x: x["y_top"])

    # === 2. 分析字号分布，确定标题层级 ===
    sizes = [lf["avg_size"] for lf in lines_features]
    size_counter = Counter(sizes)
    sorted_sizes = sorted(size_counter.items(), key=lambda x: -x[0])  # 从大到小

    # 取前3个显著不同的字号作为标题候选
    title_sizes = []
    for size, count in sorted_sizes:
        if count >= 1 and (not title_sizes or abs(size - title_sizes[-1]) > 1.0):
            title_sizes.append(size)
            if len(title_sizes) >= 3:
                break

    # 如果只有1-2种字号，补充正文字号
    if len(title_sizes) == 0:
        title_sizes = [max(sizes)]
    while len(title_sizes) < 3:
        title_sizes.append(min(sizes))

    level1_size, level2_size, level3_size = title_sizes[:3]

    # === 3. 分类每行 ===
    results = {
        "title_1": [],
        "title_2": [],
        "title_3": [],
        "chapter": [],
        "question_numbers": [],
        "questions": [],
        "options": [],
        "answers": [],
        "analyses": [],
        "body_text": []
    }

    i = 0
    while i < len(lines_features):
        line = lines_features[i]
        text = line["text"]

        # ===== 标题判断 =====
        if line["avg_size"] >= level1_size - 0.5 and (line["is_bold"] or line["is_centered"]):
            results["title_1"].append(line)
        elif line["avg_size"] >= level2_size - 0.5 and line["is_bold"]:
            results["title_2"].append(line)
        elif line["avg_size"] >= level3_size - 0.5 and line["starts_with_number"]:
            results["title_3"].append(line)
        # ===== 章节（通常带“第X章”）=====
        elif re.search(r'第[一二三四五六七八九十\d]+[章节]', text):
            results["chapter"].append(line)
        # ===== 题号（1. 2. (1) 【例1】等）=====
        elif line["starts_with_number"] and not line["starts_with_letter_option"]:
            # 合并多行题干（简单策略：连续非选项行）
            question_lines = [text]
            j = i + 1
            while j < len(lines_features):
                next_line = lines_features[j]
                if next_line["starts_with_letter_option"] or next_line["starts_with_chinese_bracket"]:
                    break
                if next_line["avg_size"] != line["avg_size"]:  # 字号突变也停止
                    break
                question_lines.append(next_line["text"])
                j += 1
            results["questions"].append({
                "number_line": line,
                "full_question": " ".join(question_lines)
            })
            i = j - 1  # 跳过已合并行
        # ===== 选项 =====
        elif line["starts_with_letter_option"]:
            results["options"].append(line)
        # ===== 解析/答案 =====
        elif "解析" in text or "解：" in text or "【解析】" in text:
            results["analyses"].append(line)
        elif "答案" in text or "答：" in text:
            results["answers"].append(line)
        else:
            results["body_text"].append(line)

        i += 1

    # === 4. 打印报告 ===
    if verbose:
        print("📑 PDF 结构化分类报告")
        print("=" * 60)

        def _print_group(name, items, key="text"):
            if items:
                print(f"\n📌 {name} ({len(items)} 项):")
                for item in items[:5]:  # 只显示前5个
                    txt = item[key] if isinstance(item, dict) else item["text"]
                    print(f"  • {txt[:60]}{'...' if len(txt) > 60 else ''}")
                if len(items) > 5:
                    print(f"    ... 还有 {len(items) - 5} 项")

        _print_group("1级标题", results["title_1"])
        _print_group("2级标题", results["title_2"])
        _print_group("3级标题", results["title_3"])
        _print_group("章节标识", results["chapter"])
        _print_group("题干", results["questions"], key="full_question")
        _print_group("选项", results["options"])
        _print_group("答案", results["answers"])
        _print_group("解析", results["analyses"])
        _print_group("普通正文", results["body_text"])

        print("\n💡 提示：本分类基于字体、字号、位置、关键词等启发式规则，适用于结构清晰的文档。")
        print("=" * 60)

    return results


if __name__ == '__main__':
    """分析PDF文档结构 """
    # 文本提取方法：
    # page.get_text("blocks") 返回文本块
    # page.get_text("words") 返回单词级别信息
    # page.get_text("dict") 返回详细结构化信息
    # 使用示例
    # pdf_path = str(TEST_DATA_DIR / "25-注会-轻1-财务成本管理[上册](第3章).pdf")
    pdf_path = str(TEST_DATA_DIR / "1711605374231.pdf")

    """ 测试 """

    # roi_bbox = (237.46, 137.87, 295.46, 177.44)
    # doc = fitz.open(pdf_path)
    # page = doc[0]
    # text = page.get_text()
    # # blocks = page.get_text('blocks', sort=True)
    # dict = page.get_text('dict', sort=True)
    # json_str = extract_json(dict, sort=True)
    # # print(json)
    # doc.close()

    # 新增功能测试
    print("\n" + "=" * 60)
    print("开始分析PDF文档结构")
    print("=" * 60)

    # 先将第一页的dict数据保存为JSON文件
    output_json_path = OUTPUT_DATA_DIR / "page_0.json"
    try:
        save_page_json(pdf_path, 0, output_json_path)
        print(f"已将第0页的dict数据保存至: {output_json_path}")
    except Exception as e:
        print(f"保存JSON文件时出错: {e}")

    # # 分析PDF结构
    # try:
    #     analysis_result = analyze_pdf_structure(pdf_path, 0)
    #     print_analysis_summary(analysis_result)
    # except Exception as e:
    #     print(f"分析PDF结构时出错: {e}")
    #     import traceback
    #
    #     traceback.print_exc()
